home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / Net / Sieve.php < prev    next >
Encoding:
PHP Script  |  2006-04-07  |  36.1 KB  |  1,215 lines

  1. <?php
  2. // +-----------------------------------------------------------------------+
  3. // | Copyright (c) 2002-2003, Richard Heyes                                |
  4. // | Copyright (c) 2006, Anish Mistry                                      |
  5. // | All rights reserved.                                                  |
  6. // |                                                                       |
  7. // | Redistribution and use in source and binary forms, with or without    |
  8. // | modification, are permitted provided that the following conditions    |
  9. // | are met:                                                              |
  10. // |                                                                       |
  11. // | o Redistributions of source code must retain the above copyright      |
  12. // |   notice, this list of conditions and the following disclaimer.       |
  13. // | o Redistributions in binary form must reproduce the above copyright   |
  14. // |   notice, this list of conditions and the following disclaimer in the |
  15. // |   documentation and/or other materials provided with the distribution.|
  16. // | o The names of the authors may not be used to endorse or promote      |
  17. // |   products derived from this software without specific prior written  |
  18. // |   permission.                                                         |
  19. // |                                                                       |
  20. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
  21. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
  22. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  23. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
  24. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  25. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
  26. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  27. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  28. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
  29. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  30. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
  31. // |                                                                       |
  32. // +-----------------------------------------------------------------------+
  33. // | Author: Richard Heyes <richard@phpguru.org>                           |
  34. // | Co-Author: Damian Fernandez Sosa <damlists@cnba.uba.ar>               |
  35. // | Co-Author: Anish Mistry <amistry@am-productions.biz>                  |
  36. // +-----------------------------------------------------------------------+
  37.  
  38. require_once('Net/Socket.php');
  39.  
  40. /**
  41. * TODO
  42. *
  43. * o supportsAuthMech()
  44. */
  45.  
  46. /**
  47. * Disconnected state
  48. * @const NET_SIEVE_STATE_DISCONNECTED
  49. */
  50. define('NET_SIEVE_STATE_DISCONNECTED',  1, true);
  51.  
  52. /**
  53. * Authorisation state
  54. * @const NET_SIEVE_STATE_AUTHORISATION
  55. */
  56. define('NET_SIEVE_STATE_AUTHORISATION', 2, true);
  57.  
  58. /**
  59. * Transaction state
  60. * @const NET_SIEVE_STATE_TRANSACTION
  61. */
  62. define('NET_SIEVE_STATE_TRANSACTION',   3, true);
  63.  
  64. /**
  65. * A class for talking to the timsieved server which
  66. * comes with Cyrus IMAP. the HAVESPACE
  67. * command which appears to be broken (Cyrus 2.0.16).
  68. * SIEVE: RFC3028 http://www.ietf.org/rfc/rfc3028.txt
  69. *
  70. * @author  Richard Heyes <richard@php.net>
  71. * @author  Damian Fernandez Sosa <damlists@cnba.uba.ar>
  72. * @author  Anish Mistry <amistry@am-productions.biz>
  73. * @access  public
  74. * @version 1.2.0
  75. * @package Net_Sieve
  76. */
  77.  
  78. class Net_Sieve
  79. {
  80.     /**
  81.     * The socket object
  82.     * @var object
  83.     */
  84.     var $_sock;
  85.  
  86.     /**
  87.     * Info about the connect
  88.     * @var array
  89.     */
  90.     var $_data;
  91.  
  92.     /**
  93.     * Current state of the connection
  94.     * @var integer
  95.     */
  96.     var $_state;
  97.  
  98.     /**
  99.     * Constructor error is any
  100.     * @var object
  101.     */
  102.     var $_error;
  103.  
  104.  
  105.     /**
  106.     * To allow class debuging
  107.     * @var boolean
  108.     */
  109.     var $_debug = false;
  110.     /**
  111.     * Allows picking up of an already established connection
  112.     * @var boolean
  113.     */
  114.     var $_bypassAuth = false;
  115.  
  116.  
  117.     /**
  118.     * The auth methods this class support
  119.     * @var array
  120.     */
  121.  
  122.     var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'PLAIN' , 'LOGIN');
  123.     //if you have problems using DIGEST-MD5 authentication  please comment the line above and uncomment the following line
  124.     //var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
  125.  
  126.     //var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');
  127.  
  128.  
  129.     /**
  130.     * The auth methods this class support
  131.     * @var array
  132.     */
  133.     var $supportedSASLAuthMethods=array('DIGEST-MD5', 'CRAM-MD5');
  134.  
  135.  
  136.  
  137.     /**
  138.     * Handles posible referral loops
  139.     * @var array
  140.     */
  141.     var $_maxReferralCount = 15;
  142.  
  143.     /**
  144.     * Constructor
  145.     * Sets up the object, connects to the server and logs in. stores
  146.     * any generated error in $this->_error, which can be retrieved
  147.     * using the getError() method.
  148.     *
  149.     * @access public
  150.     * @param  string $user      Login username
  151.     * @param  string $pass      Login password
  152.     * @param  string $host      Hostname of server
  153.     * @param  string $port      Port of server
  154.     * @param  string $logintype Type of login to perform
  155.     * @param  string $euser     Effective User (if $user=admin, login as $euser)
  156.     * @param  string $bypassAuth Skip the authentication phase.  Useful if the socket
  157.                                   is already open.
  158.     */
  159.     function Net_Sieve($user = null , $pass  = null , $host = 'localhost', $port = 2000, $logintype = '', $euser = '', $debug = false, $bypassAuth = false)
  160.     {
  161.         $this->_state = NET_SIEVE_STATE_DISCONNECTED;
  162.         $this->_data['user'] = $user;
  163.         $this->_data['pass'] = $pass;
  164.         $this->_data['host'] = $host;
  165.         $this->_data['port'] = $port;
  166.         $this->_data['logintype'] = $logintype;
  167.         $this->_data['euser'] = $euser;
  168.         $this->_sock = &new Net_Socket();
  169.         $this->_debug = $debug;
  170.         $this->_bypassAuth = $bypassAuth;
  171.         /*
  172.         * Include the Auth_SASL package.  If the package is not available,
  173.         * we disable the authentication methods that depend upon it.
  174.         */
  175.         if ((@include_once 'Auth/SASL.php') === false) {
  176.             if($this->_debug){
  177.                 echo "AUTH_SASL NOT PRESENT!\n";
  178.             }
  179.             foreach($this->supportedSASLAuthMethods as $SASLMethod){
  180.                 $pos = array_search( $SASLMethod, $this->supportedAuthMethods );
  181.                 if($this->_debug){
  182.                     echo "DISABLING METHOD $SASLMethod\n";
  183.                 }
  184.                 unset($this->supportedAuthMethods[$pos]);
  185.             }
  186.         }
  187.         if( ($user != null) && ($pass != null) ){
  188.             $this->_error = $this->_handleConnectAndLogin();
  189.         }
  190.     }
  191.  
  192.  
  193.  
  194.     /**
  195.     * Handles the errors the class can find
  196.     * on the server
  197.     *
  198.     * @access private
  199.     * @return PEAR_Error
  200.     */
  201.  
  202.     function _raiseError($msg, $code)
  203.     {
  204.         include_once 'PEAR.php';
  205.         return PEAR::raiseError($msg, $code);
  206.     }
  207.  
  208.  
  209.  
  210.  
  211.  
  212.     /**
  213.     * Handles connect and login.
  214.     * on the server
  215.     *
  216.     * @access private
  217.     * @return mixed Indexed array of scriptnames or PEAR_Error on failure
  218.     */
  219.     function _handleConnectAndLogin()
  220.     {
  221.         if($this->_bypassAuth === false) {
  222.             if (PEAR::isError($res = $this->connect($this->_data['host'] , $this->_data['port'] ))) {
  223.                 return $res;
  224.             }
  225.             if (PEAR::isError($res = $this->login($this->_data['user'], $this->_data['pass'], $this->_data['logintype'] , $this->_data['euser'] , $this->_bypassAuth) ) ) {
  226.                 return $res;
  227.             }
  228.         }
  229.         return true;
  230.  
  231.     }
  232.  
  233.  
  234.  
  235.  
  236.     /**
  237.     * Returns an indexed array of scripts currently
  238.     * on the server
  239.     *
  240.     * @access public
  241.     * @return mixed Indexed array of scriptnames or PEAR_Error on failure
  242.     */
  243.     function listScripts()
  244.     {
  245.         if (is_array($scripts = $this->_cmdListScripts())) {
  246.             $this->_active = $scripts[1];
  247.             return $scripts[0];
  248.         } else {
  249.             return $scripts;
  250.         }
  251.     }
  252.  
  253.     /**
  254.     * Returns the active script
  255.     *
  256.     * @access public
  257.     * @return mixed The active scriptname or PEAR_Error on failure
  258.     */
  259.     function getActive()
  260.     {
  261.         if (!empty($this->_active)) {
  262.             return $this->_active;
  263.  
  264.         } elseif (is_array($scripts = $this->_cmdListScripts())) {
  265.             $this->_active = $scripts[1];
  266.             return $scripts[1];
  267.         }
  268.     }
  269.  
  270.     /**
  271.     * Sets the active script
  272.     *
  273.     * @access public
  274.     * @param  string $scriptname The name of the script to be set as active
  275.     * @return mixed              true on success, PEAR_Error on failure
  276.     */
  277.     function setActive($scriptname)
  278.     {
  279.         return $this->_cmdSetActive($scriptname);
  280.     }
  281.  
  282.     /**
  283.     * Retrieves a script
  284.     *
  285.     * @access public
  286.     * @param  string $scriptname The name of the script to be retrieved
  287.     * @return mixed              The script on success, PEAR_Error on failure
  288.     */
  289.     function getScript($scriptname)
  290.     {
  291.         return $this->_cmdGetScript($scriptname);
  292.     }
  293.  
  294.     /**
  295.     * Adds a script to the server
  296.     *
  297.     * @access public
  298.     * @param  string $scriptname Name of the script
  299.     * @param  string $script     The script
  300.     * @param  bool   $makeactive Whether to make this the active script
  301.     * @return mixed              true on success, PEAR_Error on failure
  302.     */
  303.     function installScript($scriptname, $script, $makeactive = false)
  304.     {
  305.         if (PEAR::isError($res = $this->_cmdPutScript($scriptname, $script))) {
  306.             return $res;
  307.  
  308.         } elseif ($makeactive) {
  309.             return $this->_cmdSetActive($scriptname);
  310.  
  311.         } else {
  312.             return true;
  313.         }
  314.     }
  315.  
  316.     /**
  317.     * Removes a script from the server
  318.     *
  319.     * @access public
  320.     * @param  string $scriptname Name of the script
  321.     * @return mixed              True on success, PEAR_Error on failure
  322.     */
  323.     function removeScript($scriptname)
  324.     {
  325.         return $this->_cmdDeleteScript($scriptname);
  326.     }
  327.  
  328.     /**
  329.     * Returns any error that may have been generated in the
  330.     * constructor
  331.     *
  332.     * @access public
  333.     * @return mixed False if no error, PEAR_Error otherwise
  334.     */
  335.     function getError()
  336.     {
  337.         return PEAR::isError($this->_error) ? $this->_error : false;
  338.     }
  339.  
  340.     /**
  341.     * Handles connecting to the server and checking the
  342.     * response is valid.
  343.     *
  344.     * @access private
  345.     * @param  string $host Hostname of server
  346.     * @param  string $port Port of server
  347.     * @param  array  $options List of options to pass to connect
  348.     * @return mixed        True on success, PEAR_Error otherwise
  349.     */
  350.     function connect($host, $port, $options = null)
  351.     {
  352.         if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) {
  353.             $msg='Not currently in DISCONNECTED state';
  354.             $code=1;
  355.             return $this->_raiseError($msg,$code);
  356.         }
  357.  
  358.         if (PEAR::isError($res = $this->_sock->connect($host, $port, false, 5, $options))) {
  359.             return $res;
  360.         }
  361.  
  362.         if($this->_bypassAuth === false) {
  363.             $this->_state = NET_SIEVE_STATE_AUTHORISATION;
  364.             if (PEAR::isError($res = $this->_doCmd())) {
  365.                 return $res;
  366.             }
  367.         } else {
  368.             $this->_state = NET_SIEVE_STATE_TRANSACTION;
  369.         }
  370.  
  371.  
  372.         // Explicitly ask for the capabilities in case the connection
  373.         // is picked up from an existing connection.
  374.         if(PEAR::isError($res = $this->_cmdCapability() )) {
  375.             $msg='Failed to connect, server said: ' . $res->getMessage();
  376.             $code=2;
  377.             return $this->_raiseError($msg,$code);
  378.         }
  379.  
  380.         // Get logon greeting/capability and parse
  381.         $this->_parseCapability($res);
  382.  
  383.         // check if we can enable TLS via STARTTLS
  384.         if(isset($this->_capability['starttls']) && function_exists('stream_socket_enable_crypto') === true) {
  385.             if (PEAR::isError($res = $this->_startTLS())) {
  386.                 return $res;
  387.             }
  388.         }
  389.  
  390.         return true;
  391.     }
  392.  
  393.     /**
  394.     * Logs into server.
  395.     *
  396.     * @access public
  397.     * @param  string  $user          Login username
  398.     * @param  string  $pass          Login password
  399.     * @param  string  $logintype     Type of login method to use
  400.     * @param  string  $euser         Effective UID (perform on behalf of $euser)
  401.     * @param  boolean $bypassAuth    Do not perform authentication
  402.     * @return mixed                  True on success, PEAR_Error otherwise
  403.     */
  404.     function login($user, $pass, $logintype = null , $euser = '', $bypassAuth = false)
  405.     {
  406.         if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) {
  407.             $msg='Not currently in AUTHORISATION state';
  408.             $code=1;
  409.             return $this->_raiseError($msg,$code);
  410.         }
  411.  
  412.  
  413.  
  414.         if( $bypassAuth === false ){
  415.             if(PEAR::isError($res=$this->_cmdAuthenticate($user , $pass , $logintype, $euser ) ) ){
  416.                 return $res;
  417.             }
  418.         }
  419.         $this->_state = NET_SIEVE_STATE_TRANSACTION;
  420.         return true;
  421.     }
  422.  
  423.  
  424.  
  425.      /* Handles the authentication using any known method
  426.      *
  427.      * @param string The userid to authenticate as.
  428.      * @param string The password to authenticate with.
  429.      * @param string The method to use ( if $usermethod == '' then the class chooses the best method (the stronger is the best ) )
  430.      * @param string The effective uid to authenticate as.
  431.      *
  432.      * @return mixed  string or PEAR_Error
  433.      *
  434.      * @access private
  435.      * @since  1.0
  436.      */
  437.     function _cmdAuthenticate($uid , $pwd , $userMethod = null , $euser = '' )
  438.     {
  439.  
  440.  
  441.         if ( PEAR::isError( $method = $this->_getBestAuthMethod($userMethod) ) ) {
  442.             return $method;
  443.         }
  444.         switch ($method) {
  445.             case 'DIGEST-MD5':
  446.                 $result = $this->_authDigest_MD5( $uid , $pwd , $euser );
  447.                 return $result;
  448.                 break;
  449.             case 'CRAM-MD5':
  450.                 $result = $this->_authCRAM_MD5( $uid , $pwd, $euser);
  451.                 break;
  452.             case 'LOGIN':
  453.                 $result = $this->_authLOGIN( $uid , $pwd , $euser );
  454.                 break;
  455.             case 'PLAIN':
  456.                 $result = $this->_authPLAIN( $uid , $pwd , $euser );
  457.                 break;
  458.             default :
  459.                 $result = new PEAR_Error( "$method is not a supported authentication method" );
  460.                 break;
  461.         }
  462.  
  463.  
  464.         if (PEAR::isError($res = $this->_doCmd() )) {
  465.             return $res;
  466.         }
  467.         return $result;
  468.     }
  469.  
  470.  
  471.  
  472.  
  473.  
  474.  
  475.  
  476.  
  477.  
  478.      /* Authenticates the user using the PLAIN method.
  479.      *
  480.      * @param string The userid to authenticate as.
  481.      * @param string The password to authenticate with.
  482.      * @param string The effective uid to authenticate as.
  483.      *
  484.      * @return array Returns an array containing the response
  485.      *
  486.      * @access private
  487.      * @since  1.0
  488.      */
  489.     function _authPLAIN($user, $pass , $euser )
  490.     {
  491.  
  492.         if ($euser != '') {
  493.             $cmd=sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode($euser . chr(0) . $user . chr(0) . $pass ) ) ;
  494.         } else {
  495.             $cmd=sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode( chr(0) . $user . chr(0) . $pass ) );
  496.         }
  497.         return  $this->_sendCmd( $cmd ) ;
  498.  
  499.     }
  500.  
  501.  
  502.  
  503.      /* Authenticates the user using the PLAIN method.
  504.      *
  505.      * @param string The userid to authenticate as.
  506.      * @param string The password to authenticate with.
  507.      * @param string The effective uid to authenticate as.
  508.      *
  509.      * @return array Returns an array containing the response
  510.      *
  511.      * @access private
  512.      * @since  1.0
  513.      */
  514.     function _authLOGIN($user, $pass , $euser )
  515.     {
  516.         $this->_sendCmd('AUTHENTICATE "LOGIN"');
  517.         $this->_doCmd(sprintf('"%s"', base64_encode($user)));
  518.         $this->_doCmd(sprintf('"%s"', base64_encode($pass)));
  519.  
  520.     }
  521.  
  522.  
  523.  
  524.  
  525.      /* Authenticates the user using the CRAM-MD5 method.
  526.      *
  527.      * @param string The userid to authenticate as.
  528.      * @param string The password to authenticate with.
  529.      * @param string The cmdID.
  530.      *
  531.      * @return array Returns an array containing the response
  532.      *
  533.      * @access private
  534.      * @since  1.0
  535.      */
  536.     function _authCRAM_MD5($uid, $pwd, $euser)
  537.     {
  538.  
  539.         if ( PEAR::isError( $challenge = $this->_doCmd( 'AUTHENTICATE "CRAM-MD5"' ) ) ) {
  540.             $this->_error=challenge ;
  541.             return challenge ;
  542.         }
  543.         $challenge=trim($challenge);
  544.         $challenge = base64_decode( trim($challenge) );
  545.         $cram = &Auth_SASL::factory('crammd5');
  546.         if ( PEAR::isError($resp=$cram->getResponse( $uid , $pwd , $challenge ) ) ) {
  547.             $this->_error=$resp;
  548.             return $resp;
  549.         }
  550.         $auth_str = base64_encode( $resp );
  551.         if ( PEAR::isError($error = $this->_sendStringResponse( $auth_str  ) ) ) {
  552.             $this->_error=$error;
  553.             return $error;
  554.         }
  555.  
  556.     }
  557.  
  558.  
  559.  
  560.      /* Authenticates the user using the DIGEST-MD5 method.
  561.      *
  562.      * @param string The userid to authenticate as.
  563.      * @param string The password to authenticate with.
  564.      * @param string The efective user
  565.      *
  566.      * @return array Returns an array containing the response
  567.      *
  568.      * @access private
  569.      * @since  1.0
  570.      */
  571.     function _authDigest_MD5($uid, $pwd, $euser)
  572.     {
  573.  
  574.         if ( PEAR::isError( $challenge = $this->_doCmd('AUTHENTICATE "DIGEST-MD5"') ) ) {
  575.             $this->_error=challenge ;
  576.             return challenge ;
  577.         }
  578.         $challenge = base64_decode( $challenge );
  579.         $digest = &Auth_SASL::factory('digestmd5');
  580.  
  581.  
  582.         if(PEAR::isError($param=$digest->getResponse($uid, $pwd, $challenge, "localhost", "sieve" , $euser) )) {
  583.             return $param;
  584.         }
  585.         $auth_str = base64_encode($param);
  586.  
  587.         if ( PEAR::isError($error = $this->_sendStringResponse( $auth_str  ) ) ) {
  588.             $this->_error=$error;
  589.             return $error;
  590.         }
  591.  
  592.  
  593.  
  594.         if ( PEAR::isError( $challenge = $this->_doCmd() ) ) {
  595.             $this->_error=$challenge ;
  596.             return $challenge ;
  597.         }
  598.  
  599.         if( strtoupper(substr($challenge,0,2))== 'OK' ){
  600.                 return true;
  601.         }
  602.  
  603.  
  604.         /*
  605.         * We don't use the protocol's third step because SIEVE doesn't allow
  606.         * subsequent authentication, so we just silently ignore it.
  607.         */
  608.  
  609.  
  610.         if ( PEAR::isError($error = $this->_sendStringResponse( '' ) ) ) {
  611.             $this->_error=$error;
  612.             return $error;
  613.         }
  614.  
  615.         if (PEAR::isError($res = $this->_doCmd() )) {
  616.             return $res;
  617.         }
  618.  
  619.  
  620.     }
  621.  
  622.  
  623.  
  624.  
  625.     /**
  626.     * Removes a script from the server
  627.     *
  628.     * @access private
  629.     * @param  string $scriptname Name of the script to delete
  630.     * @return mixed              True on success, PEAR_Error otherwise
  631.     */
  632.     function _cmdDeleteScript($scriptname)
  633.     {
  634.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  635.             $msg='Not currently in AUTHORISATION state';
  636.             $code=1;
  637.             return $this->_raiseError($msg,$code);
  638.         }
  639.         if (PEAR::isError($res = $this->_doCmd(sprintf('DELETESCRIPT "%s"', $scriptname) ) )) {
  640.             return $res;
  641.         }
  642.         return true;
  643.     }
  644.  
  645.     /**
  646.     * Retrieves the contents of the named script
  647.     *
  648.     * @access private
  649.     * @param  string $scriptname Name of the script to retrieve
  650.     * @return mixed              The script if successful, PEAR_Error otherwise
  651.     */
  652.     function _cmdGetScript($scriptname)
  653.     {
  654.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  655.             $msg='Not currently in AUTHORISATION state';
  656.             $code=1;
  657.             return $this->_raiseError($msg,$code);
  658.         }
  659.  
  660.         if (PEAR::isError($res = $this->_doCmd(sprintf('GETSCRIPT "%s"', $scriptname) ) ) ) {
  661.             return $res;
  662.         }
  663.  
  664.         return preg_replace('/{[0-9]+}\r\n/', '', $res);
  665.     }
  666.  
  667.     /**
  668.     * Sets the ACTIVE script, ie the one that gets run on new mail
  669.     * by the server
  670.     *
  671.     * @access private
  672.     * @param  string $scriptname The name of the script to mark as active
  673.     * @return mixed              True on success, PEAR_Error otherwise
  674.     */
  675.     function _cmdSetActive($scriptname)
  676.     {
  677.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  678.             $msg='Not currently in AUTHORISATION state';
  679.             $code=1;
  680.             return $this->_raiseError($msg,$code);
  681.         }
  682.  
  683.         if (PEAR::isError($res = $this->_doCmd(sprintf('SETACTIVE "%s"', $scriptname) ) ) ) {
  684.             return $res;
  685.         }
  686.  
  687.         $this->_activeScript = $scriptname;
  688.         return true;
  689.     }
  690.  
  691.     /**
  692.     * Sends the LISTSCRIPTS command
  693.     *
  694.     * @access private
  695.     * @return mixed Two item array of scripts, and active script on success,
  696.     *               PEAR_Error otherwise.
  697.     */
  698.     function _cmdListScripts()
  699.     {
  700.  
  701.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  702.             $msg='Not currently in AUTHORISATION state';
  703.             $code=1;
  704.             return $this->_raiseError($msg,$code);
  705.         }
  706.  
  707.         $scripts = array();
  708.         $activescript = null;
  709.  
  710.         if (PEAR::isError($res = $this->_doCmd('LISTSCRIPTS'))) {
  711.             return $res;
  712.         }
  713.  
  714.         $res = explode("\r\n", $res);
  715.  
  716.         foreach ($res as $value) {
  717.             if (preg_match('/^"(.*)"( ACTIVE)?$/i', $value, $matches)) {
  718.                 $scripts[] = $matches[1];
  719.                 if (!empty($matches[2])) {
  720.                     $activescript = $matches[1];
  721.                 }
  722.             }
  723.         }
  724.  
  725.         return array($scripts, $activescript);
  726.     }
  727.  
  728.     /**
  729.     * Sends the PUTSCRIPT command to add a script to
  730.     * the server.
  731.     *
  732.     * @access private
  733.     * @param  string $scriptname Name of the new script
  734.     * @param  string $scriptdata The new script
  735.     * @return mixed              True on success, PEAR_Error otherwise
  736.     */
  737.     function _cmdPutScript($scriptname, $scriptdata)
  738.     {
  739.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  740.             $msg='Not currently in TRANSACTION state';
  741.             $code=1;
  742.             return $this->_raiseError($msg,$code);
  743.         }
  744.  
  745.         if (PEAR::isError($res = $this->_doCmd(sprintf("PUTSCRIPT \"%s\" {%d+}\r\n%s", $scriptname, strlen($scriptdata),$scriptdata ) ))) {
  746.             return $res;
  747.         }
  748.  
  749.         return true;
  750.     }
  751.  
  752.     /**
  753.     * Sends the LOGOUT command and terminates the connection
  754.     *
  755.     * @access private
  756.     * @return mixed True on success, PEAR_Error otherwise
  757.     */
  758.     function _cmdLogout($sendLogoutCMD=true)
  759.     {
  760.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  761.             $msg='Not currently connected';
  762.             $code=1;
  763.             return $this->_raiseError($msg,$code);
  764.             //return PEAR::raiseError('Not currently connected');
  765.         }
  766.  
  767.         if($sendLogoutCMD){
  768.             if (PEAR::isError($res = $this->_doCmd('LOGOUT'))) {
  769.                 return $res;
  770.             }
  771.         }
  772.  
  773.         $this->_sock->disconnect();
  774.         $this->_state = NET_SIEVE_STATE_DISCONNECTED;
  775.         return true;
  776.     }
  777.  
  778.     /**
  779.     * Sends the CAPABILITY command
  780.     *
  781.     * @access private
  782.     * @return mixed True on success, PEAR_Error otherwise
  783.     */
  784.     function _cmdCapability()
  785.     {
  786.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  787.             $msg='Not currently connected';
  788.             $code=1;
  789.             return $this->_raiseError($msg,$code);
  790.         }
  791.  
  792.         if (PEAR::isError($res = $this->_doCmd('CAPABILITY'))) {
  793.             return $res;
  794.         }
  795.         $this->_parseCapability($res);
  796.         return true;
  797.     }
  798.  
  799.  
  800.     /**
  801.     * Checks if the server has space to store the script
  802.     * by the server
  803.     *
  804.     * @access public
  805.     * @param  string $scriptname The name of the script to mark as active
  806.     * @return mixed              True on success, PEAR_Error otherwise
  807.     */
  808.     function haveSpace($scriptname,$quota)
  809.     {
  810.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  811.             $msg='Not currently in TRANSACTION state';
  812.             $code=1;
  813.             return $this->_raiseError($msg,$code);
  814.         }
  815.  
  816.         if (PEAR::isError($res = $this->_doCmd(sprintf('HAVESPACE "%s" %s', $scriptname, $quota) ) ) ) {
  817.         //if (PEAR::isError($res = $this->_doCmd(sprintf('HAVESPACE %d "%s"',  $quota,$scriptname ) ) ) ) {
  818.             return $res;
  819.         }
  820.  
  821.         return true;
  822.     }
  823.  
  824.  
  825.  
  826.  
  827.     /**
  828.     * Parses the response from the capability command. Storesq
  829.     * the result in $this->_capability
  830.     *
  831.     * @access private
  832.     */
  833.     function _parseCapability($data)
  834.     {
  835.         $data = preg_split('/\r?\n/', $data, -1, PREG_SPLIT_NO_EMPTY);
  836.  
  837.         for ($i = 0; $i < count($data); $i++) {
  838.             if (preg_match('/^"([a-z]+)"( "(.*)")?$/i', $data[$i], $matches)) {
  839.                 switch (strtolower($matches[1])) {
  840.                     case 'implementation':
  841.                         $this->_capability['implementation'] = $matches[3];
  842.                         break;
  843.  
  844.                     case 'sasl':
  845.                         $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
  846.                         break;
  847.  
  848.                     case 'sieve':
  849.                         $this->_capability['extensions'] = preg_split('/\s+/', $matches[3]);
  850.                         break;
  851.  
  852.                     case 'starttls':
  853.                         $this->_capability['starttls'] = true;
  854.                         break;
  855.                 }
  856.             }
  857.         }
  858.     }
  859.  
  860.     /**
  861.     * Sends a command to the server
  862.     *
  863.     * @access private
  864.     * @param string $cmd The command to send
  865.     */
  866.     function _sendCmd($cmd)
  867.     {
  868.         $status = $this->_sock->getStatus();
  869.         if (PEAR::isError($status) || $status['eof']) {
  870.             return new PEAR_Error( 'Failed to write to socket: (connection lost!) ' );
  871.         }
  872.         if ( PEAR::isError( $error = $this->_sock->write( $cmd . "\r\n" ) ) ) {
  873.             return new PEAR_Error( 'Failed to write to socket: ' . $error->getMessage() );
  874.         }
  875.  
  876.         if( $this->_debug ){
  877.             // C: means this data was sent by  the client (this class)
  878.             echo "C:$cmd\n";
  879.         }
  880.         return true;
  881.  
  882.  
  883.     }
  884.  
  885.  
  886.  
  887.     /**
  888.     * Sends a string response to the server
  889.     *
  890.     * @access private
  891.     * @param string $cmd The command to send
  892.     */
  893.     function _sendStringResponse($str)
  894.     {
  895.         $response='{' .  strlen($str) . "+}\r\n" . $str  ;
  896.         return $this->_sendCmd($response);
  897.     }
  898.  
  899.  
  900.  
  901.  
  902.     function _recvLn()
  903.     {
  904.         $lastline='';
  905.         if (PEAR::isError( $lastline = $this->_sock->gets( 8192 ) ) ) {
  906.             return new PEAR_Error('Failed to write to socket: ' . $lastline->getMessage() );
  907.         }
  908.         $lastline=rtrim($lastline);
  909.         if($this->_debug){
  910.             // S: means this data was sent by  the IMAP Server
  911.             echo "S:$lastline\n" ;
  912.         }
  913.  
  914. /*        if( $lastline === '' ){
  915.             return new PEAR_Error('Failed to receive from the  socket: '  );
  916.         }
  917. */
  918.         return $lastline;
  919.     }
  920.  
  921.  
  922.  
  923.  
  924.  
  925.     /**
  926.     * Send a command and retrieves a response from the server.
  927.     *
  928.     *
  929.     * @access private
  930.     * @param string $cmd The command to send
  931.     * @return mixed Reponse string if an OK response, PEAR_Error if a NO response
  932.     */
  933.     function _doCmd($cmd = '' )
  934.     {
  935.  
  936.         $referralCount=0;
  937.         while($referralCount < $this->_maxReferralCount ){
  938.  
  939.  
  940.             if($cmd != '' ){
  941.                 if(PEAR::isError($error = $this->_sendCmd($cmd) )) {
  942.                     return $error;
  943.                 }
  944.             }
  945.             $response = '';
  946.  
  947.             while (true) {
  948.                     if(PEAR::isError( $line=$this->_recvLn() )){
  949.                         return $line;
  950.                     }
  951.                     if ('ok' === strtolower(substr($line, 0, 2))) {
  952.                         $response .= $line;
  953.                         return rtrim($response);
  954.  
  955.                     } elseif ('no' === strtolower(substr($line, 0, 2))) {
  956.                         // Check for string literal error message
  957.                         if (preg_match('/^no {([0-9]+)\+?}/i', $line, $matches)) {
  958.                             $line .= str_replace("\r\n", ' ', $this->_sock->read($matches[1] + 2 ));
  959.                             if($this->_debug){
  960.                                 echo "S:$line\n";
  961.                             }
  962.                         }
  963.                         $msg=trim($response . substr($line, 2));
  964.                         $code=3;
  965.                         return $this->_raiseError($msg,$code);
  966.                     } elseif ('bye' === strtolower(substr($line, 0, 3))) {
  967.  
  968.                         if(PEAR::isError($error = $this->disconnect(false) ) ){
  969.                             $msg="Can't handle bye, The error was= " . $error->getMessage() ;
  970.                             $code=4;
  971.                             return $this->_raiseError($msg,$code);
  972.                         }
  973.                         //if (preg_match('/^bye \(referral "([^"]+)/i', $line, $matches)) {
  974.                         if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) {
  975.                             // Check for referral, then follow it.  Otherwise, carp an error.
  976.                             // Replace the old host with the referral host preserving any protocol prefix
  977.                             $this->_data['host'] = preg_replace('/\w+(?!(\w|\:\/\/)).*/',$matches[2],$this->_data['host']);
  978.                            if (PEAR::isError($error = $this->_handleConnectAndLogin() ) ){
  979.                                 $msg="Can't follow referral to " . $this->_data['host'] . ", The error was= " . $error->getMessage() ;
  980.                                 $code=5;
  981.                                 return $this->_raiseError($msg,$code);
  982.                             }
  983.                             break;
  984.                             // Retry the command
  985.                             if(PEAR::isError($error = $this->_sendCmd($cmd) )) {
  986.                                 return $error;
  987.                             }
  988.                             continue;
  989.                         }
  990.                         $msg=trim($response . $line);
  991.                         $code=6;
  992.                         return $this->_raiseError($msg,$code);
  993.                     } elseif (preg_match('/^{([0-9]+)\+?}/i', $line, $matches)) {
  994.                         // Matches String Responses.
  995.                         //$line = str_replace("\r\n", ' ', $this->_sock->read($matches[1] + 2 ));
  996.                         $line = $this->_sock->read($matches[1] + 2 );
  997.                         if($this->_debug){
  998.                             echo "S:$line\n";
  999.                         }
  1000.                         return $line;
  1001.                     }
  1002.                     $response .= $line . "\r\n";
  1003.                     $referralCount++;
  1004.                 }
  1005.         }
  1006.         $msg="Max referral count reached ($referralCount times) Cyrus murder loop error?";
  1007.         $code=7;
  1008.         return $this->_raiseError($msg,$code);
  1009.     }
  1010.  
  1011.  
  1012.  
  1013.  
  1014.     /**
  1015.     * Sets the bebug state
  1016.     *
  1017.     * @access public
  1018.     * @return void
  1019.     */
  1020.     function setDebug($debug = true)
  1021.     {
  1022.         $this->_debug = $debug;
  1023.     }
  1024.  
  1025.     /**
  1026.     * Disconnect from the Sieve server
  1027.     *
  1028.     * @access public
  1029.     * @param  string $scriptname The name of the script to be set as active
  1030.     * @return mixed              true on success, PEAR_Error on failure
  1031.     */
  1032.     function disconnect($sendLogoutCMD=true)
  1033.     {
  1034.         return $this->_cmdLogout($sendLogoutCMD);
  1035.     }
  1036.  
  1037.  
  1038.     /**
  1039.      * Returns the name of the best authentication method that the server
  1040.      * has advertised.
  1041.      *
  1042.      * @param string if !=null,authenticate with this method ($userMethod).
  1043.      *
  1044.      * @return mixed    Returns a string containing the name of the best
  1045.      *                  supported authentication method or a PEAR_Error object
  1046.      *                  if a failure condition is encountered.
  1047.      * @access private
  1048.      * @since  1.0
  1049.      */
  1050.     function _getBestAuthMethod($userMethod = null)
  1051.     {
  1052.  
  1053.        if( isset($this->_capability['sasl']) ){
  1054.            $serverMethods=$this->_capability['sasl'];
  1055.        }else{
  1056.            // if the server don't send an sasl capability fallback to login auth
  1057.            //return 'LOGIN';
  1058.            return new PEAR_Error("This server don't support any Auth methods SASL problem?");
  1059.        }
  1060.  
  1061.         if($userMethod != null ){
  1062.             $methods = array();
  1063.             $methods[] = $userMethod;
  1064.         }else{
  1065.  
  1066.             $methods = $this->supportedAuthMethods;
  1067.         }
  1068.         if( ($methods != null) && ($serverMethods != null)){
  1069.             foreach ( $methods as $method ) {
  1070.                 if ( in_array( $method , $serverMethods ) ) {
  1071.                     return $method;
  1072.                 }
  1073.             }
  1074.             $serverMethods=implode(',' , $serverMethods );
  1075.             $myMethods=implode(',' ,$this->supportedAuthMethods);
  1076.             return new PEAR_Error("$method NOT supported authentication method!. This server " .
  1077.                 "supports these methods= $serverMethods, but I support $myMethods");
  1078.         }else{
  1079.             return new PEAR_Error("This server don't support any Auth methods");
  1080.         }
  1081.     }
  1082.  
  1083.  
  1084.  
  1085.  
  1086.  
  1087.     /**
  1088.     * Return the list of extensions the server supports
  1089.     *
  1090.     * @access public
  1091.     * @return mixed              array  on success, PEAR_Error on failure
  1092.     */
  1093.     function getExtensions()
  1094.     {
  1095.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  1096.             $msg='Not currently connected';
  1097.             $code=7;
  1098.             return $this->_raiseError($msg,$code);
  1099.         }
  1100.  
  1101.         return $this->_capability['extensions'];
  1102.     }
  1103.  
  1104.  
  1105.  
  1106.  
  1107.  
  1108.     /**
  1109.     * Return true if tyhe server has that extension
  1110.     *
  1111.     * @access public
  1112.     * @param string  the extension to compare
  1113.     * @return mixed              array  on success, PEAR_Error on failure
  1114.     */
  1115.     function hasExtension($extension)
  1116.     {
  1117.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  1118.             $msg='Not currently connected';
  1119.             $code=7;
  1120.             return $this->_raiseError($msg,$code);
  1121.         }
  1122.  
  1123.         if(is_array($this->_capability['extensions'] ) ){
  1124.             foreach( $this->_capability['extensions'] as $ext){
  1125.                 if( trim( strtolower( $ext ) ) === trim( strtolower( $extension ) ) )
  1126.                     return true;
  1127.             }
  1128.         }
  1129.         return false;
  1130.     }
  1131.  
  1132.  
  1133.  
  1134.     /**
  1135.     * Return the list of auth methods the server supports
  1136.     *
  1137.     * @access public
  1138.     * @return mixed              array  on success, PEAR_Error on failure
  1139.     */
  1140.     function getAuthMechs()
  1141.     {
  1142.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  1143.             $msg='Not currently connected';
  1144.             $code=7;
  1145.             return $this->_raiseError($msg,$code);
  1146.         }
  1147.         if(!isset($this->_capability['sasl']) ){
  1148.             $this->_capability['sasl']=array();
  1149.         }
  1150.         return $this->_capability['sasl'];
  1151.     }
  1152.  
  1153.  
  1154.  
  1155.  
  1156.  
  1157.     /**
  1158.     * Return true if the server has that extension
  1159.     *
  1160.     * @access public
  1161.     * @param string  the extension to compare
  1162.     * @return mixed              array  on success, PEAR_Error on failure
  1163.     */
  1164.     function hasAuthMech($method)
  1165.     {
  1166.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  1167.             $msg='Not currently connected';
  1168.             $code=7;
  1169.             return $this->_raiseError($msg,$code);
  1170.             //return PEAR::raiseError('Not currently connected');
  1171.         }
  1172.  
  1173.         if(is_array($this->_capability['sasl'] ) ){
  1174.             foreach( $this->_capability['sasl'] as $ext){
  1175.                 if( trim( strtolower( $ext ) ) === trim( strtolower( $method ) ) )
  1176.                     return true;
  1177.             }
  1178.         }
  1179.         return false;
  1180.     }
  1181.  
  1182.     /**
  1183.     * Return true if the TLS negotiation was successful
  1184.     *
  1185.     * @access public
  1186.     * @return mixed              true on success, PEAR_Error on failure
  1187.     */
  1188.     function _startTLS()
  1189.     {
  1190.         if (PEAR::isError($res = $this->_doCmd("STARTTLS"))) {
  1191.             return $res;
  1192.         }
  1193.  
  1194.         if(stream_socket_enable_crypto($this->_sock->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT) == false) {
  1195.             $msg='Failed to establish TLS connection';
  1196.             $code=2;
  1197.             return $this->_raiseError($msg,$code);
  1198.         }
  1199.  
  1200.         if($this->_debug === true) {
  1201.             echo "STARTTLS Negotiation Successful\n";
  1202.         }
  1203.  
  1204.         // RFC says we need to query the server capabilities again
  1205.         if(PEAR::isError($res = $this->_cmdCapability() )) {
  1206.             $msg='Failed to connect, server said: ' . $res->getMessage();
  1207.             $code=2;
  1208.             return $this->_raiseError($msg,$code);
  1209.         }
  1210.         return true;
  1211.     }
  1212.  
  1213. }
  1214. ?>
  1215.